package fons.model.ontology;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.log4j.Logger;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import fons.model.ontology.interfaces.DecisionMaker;
import fons.navigator.views.ViewRequirement;
import fons.navigator.views.ViewRequirement.Type;
import fons.navigator.views.ViewRequirements;

public class ViewModel implements DecisionMaker {

	private final Context context;
	private final Map<String, Set<Dimension>> dimensions = Maps.newHashMap();
	//private final static Map<Context, Map<String, Selection>> selections = Maps.newHashMap();
	private final static Logger logger = Logger.getLogger(ViewModel.class);
	
	private AbstractView abstractView;
	
	ViewModel (final Context context) {
		this.context = context;
		
		/*if (!selections.containsKey(context)) {
			selections.put(context, Maps.<String, Selection>newHashMap());
		}*/
	}
		
	public void setAbstractView (final AbstractView abstractView) {
		this.abstractView = abstractView;
	}
	
	public void setDimensions (final String name, final Set<Dimension> dimensions) {
		if (!this.dimensions.containsKey(name)) {
			this.dimensions.put(name, Sets.<Dimension>newHashSet());
		}
		this.dimensions.get(name).addAll(dimensions);
	}
	
	public void setDimensions (final String name, final DimensionCollection dimensions) {
		if (!this.dimensions.containsKey(name)) {
			this.dimensions.put(name, Sets.<Dimension>newHashSet());
		}		
		this.dimensions.get(name).addAll(dimensions.get());
	}
	
	public void constrainDimensions(String identifier, Predicate<Dimension> predicate) {
		final Set<Dimension> dimensions = this.require(identifier);
		final Set<Dimension> constrainedDimensions = Sets.newHashSet();
		
		for (Dimension d: dimensions) {
			if (predicate.apply(d)) {
				constrainedDimensions.add(d);
			}
		}
		this.dimensions.put(identifier, constrainedDimensions);
	}
	
	public void constrainDimensions(String identifier, Function<Set<Dimension>, Set<Dimension>> filter) {
		this.dimensions.put(identifier, filter.apply(this.require(identifier)));
	}
	
	public void constrainForView () {
		abstractView.constrain(dimensions);
	}
	
	public Set<Dimension> require (String dimensionIdentifier) {
		StringBuilder msg = new StringBuilder();
		if (dimensions.get(dimensionIdentifier) == null) {
			msg.append(dimensionIdentifier + " wurde nicht vom Benutzer definiert. ");
			
			final ViewRequirements requirements = abstractView.getClass().getAnnotation(ViewRequirements.class);
			for (ViewRequirement requirement: requirements.requirements()) {
				if (requirement.name().equals(dimensionIdentifier)) {
					if (requirement.type() == Type.COLLECTION) {
						if (requirement.defaultCollection().length != 0) {
							msg.append("Verwende Standarddefinition: ");
							
							final Set<Dimension> result = Sets.newHashSet();
							boolean first = true;
							for (String dimName: requirement.defaultCollection()) {
								if (context.existsDimension(dimName)) {
									if (!first) msg.append(", ");
									result.add(context.getDimension(dimName));
									msg.append(dimName);
									
								} else {
									logger.warn(requirement.name() + " ist nicht definiert." +
											"Sie m&uuml;ssen diese in den Konfigurationsdateien definieren");
									logger.error("Nicht definierte Dimension: " + dimName);
									logger.info(requirement.name() + ": " + requirement.description() + "<br/>" +
											(requirement.comment() != ViewRequirement.NONE ? requirement.comment() : ""));
									return Sets.<Dimension>newHashSet();
								}
								
								first = false;
							}
							
							logger.info(msg.toString());  
							logger.info(requirement.name() + ": " + requirement.description() + "<br/>" +
									(requirement.comment() != ViewRequirement.NONE ? requirement.comment() : ""));
							return result;
						}
					}
				}
			}
			return Sets.<Dimension>newHashSet();	
		}

		return Collections.unmodifiableSet(dimensions.get(dimensionIdentifier));
	}
	
	public List<Set<Dimension>> requireAll (String startingWith) {
		List<Set<Dimension>> retVal = Lists.newLinkedList();
		for (Entry<String, Set<Dimension>> entry: dimensions.entrySet()) {
			if (entry.getKey().startsWith(startingWith)) {
				retVal.add(entry.getValue());
			}
		}
		return retVal;
	}
	
	public Collection<String> getDimensionIdentifiers() {
		return Collections.unmodifiableSet(this.dimensions.keySet());
	}
		
	public Context getContext() {
		return context;
	}

	public void update() {
		this.abstractView.update();
	}

	@Override
	public SelectionDefinition chooseSelection(
			Collection<SelectionDefinition> near, Set<Dimension> dimSet) {
		
		return abstractView.chooseSelection(near, dimSet);
	}

	@Override
	public SelectionDefinition autocompleteSelection(SelectionDefinition next,
			Set<Dimension> dimSet) {
		
		return abstractView.autocompleteSelection(next, dimSet);
	}
}
